/*
 * 首先介绍MaQueOS的物理内存管理机制，包括物理内存的初始化、申请和释放。
 * 因为CPU访问的指令和数据存放在物理内存中，但是CPU使用的是虚拟地址，所以，最终需要利用硬件将虚拟地址转换为物理地址。
 * LoongArch架构支持两种将虚拟地址转换为物理地址的模式，这里在物理内存初始化的过程中设置了MaQueOS在内核态下使用的虚拟地址到物理地址的地址转换模式。
 * 对于物理内存的申请和释放，MaQueOS实现了两个接口函数：get_page函数和free_page函数，其中get_page函数用于申请一个空闲物理页，free_page函数用于释放一个物理页。
 *
 * 物理内存（Physical Memory）是计算机上重要的硬件资源之一，用于在计算机运行过程中存储代码和数据。
 * MaQueOS支持对128MB物理内存进行申请和释放。
 * 内核的二进制执行代码被加载到0x200000地址处。
 *
 * 处理器中的内存管理单元（Memory Management Unit，MMU）将CPU访问的虚拟地址转换为物理地址。
 * LoongArch架构中的MMU支持两种地址转换模式：直接地址翻译模式和映射地址翻译模式。
 * 其中，映射地址翻译模式又包括直接映射地址翻译模式（以下简称直接映射）和页表映射地址翻译模式（以下简称页表映射）。
 * 当MMU处于映射地址翻译模式时，会优先选择直接映射，若无法进行直接映射，则选择页表映射。
 *
 * MaQueOS采用映射地址翻译模式进行虚拟地址到物理地址的转换。
 * 在内核态下采用直接映射，即虚拟地址与物理地址是一一映射关系；在用户态下采用页表映射。
 * 在LoongArch架构中，有4个特权级，分别是PLV0~PLV3。
 * MaQueOS只使用了特权级PLV0和PLV3，其中，内核态运行在特权级PLV0上，用户态运行在特权级PLV3上。
 * LoongArch架构提供了4个用于为不同特权级配置直接映射的寄存器DMW0~DMW3。
 * 如前所述，因为只有在内核态下采用直接映射，所以MaQueOS通过使用DMW0寄存器将内核态下使用的地址转换模式设置为直接映射。
 */

#include <xtos.h>

#define CSR_BADV 0x7
#define CSR_PWCL 0x1c
#define CSR_DMW0 0x180
#define CSR_DMW3 0x183
#define CSR_DMW0_PLV0 (1UL << 0)
#define MEMORY_SIZE 0x8000000
#define NR_PAGE (MEMORY_SIZE >> 12) /* 页的大小为4KB */
#define KERNEL_START_PAGE (0x200000UL >> 12)
#define KERNEL_END_PAGE (0x300000UL >> 12)
#define ENTRY_SIZE 8
#define PWCL_PTBASE 12
#define PWCL_PTWIDTH 9
#define PWCL_PDBASE 21
#define PWCL_PDWIDTH 9
#define PWCL_EWIDTH 0
#define ENTRYS 512

char mem_map[NR_PAGE];  /* 记录所有内存页的状态，0为空闲，1为占用。页号即为索引。 */
extern struct process *current;
extern struct shmem shmem_table[NR_SHMEM];

unsigned long get_page() {
	unsigned long page;
	unsigned long i;

	for (i = NR_PAGE - 1; i >= 0; i--) {
		if (mem_map[i] != 0) {
			continue;
		}

		mem_map[i] = 1;
		page = (i << 12) | DMW_MASK;
		set_mem((char *)page, 0, PAGE_SIZE);

		return page;
	}

	panic("panic: out of memory!\n");
	return 0;
}

void free_page(unsigned long page) {
	unsigned long i;

	i = (page & ~DMW_MASK) >> 12;
	if (!mem_map[i]) {
		panic("panic: try to free free page!\n");
	}
	mem_map[i]--;
}

void share_page(unsigned long page) {
	unsigned long i;

	i = (page & ~DMW_MASK) >> 12;
	if (!mem_map[i]) {
		panic("panic: try to share free page!\n");
	}
	mem_map[i]++;
}

int is_share_page(unsigned long page) {
	unsigned long i;

	i = (page & ~DMW_MASK) >> 12;
	if (mem_map[i] > 1) {
		return 1;
	} else {
		return 0;
	}
}

unsigned long *get_pte(struct process *p, unsigned long u_vaddr) {
	unsigned long pd, pt;
	unsigned long *pde, *pte;

	pd = p->page_directory;
	pde = (unsigned long *)(pd + ((u_vaddr >> 21) & 0x1ff) * ENTRY_SIZE);

	if (*pde) {
		pt = *pde | DMW_MASK;
	} else {
		pt = get_page();
		*pde = pt & ~DMW_MASK;
	}

	pte = (unsigned long *)(pt + ((u_vaddr >> 12) & 0x1ff) * ENTRY_SIZE);
	return pte;
}

void put_page(struct process *p, unsigned long u_vaddr, unsigned long k_vaddr, unsigned long attr) {
	unsigned long *pte;

	pte = get_pte(p, u_vaddr);
	if (*pte) {
		panic("panic: try to remap!\n");
	}
	*pte = (k_vaddr & ~DMW_MASK) | attr;
	invalidate();
}

void free_page_table(struct process *p) {
	unsigned long pd, pt;
	unsigned long *pde, *pte;
	unsigned long page;

	pd = p->page_directory;
	pde = (unsigned long *)pd;
	for (int i = 0; i < ENTRYS; i++, pde++) {
		if (*pde == 0) {
			continue;
		}

		pt = *pde | DMW_MASK;
		pte = (unsigned long *)pt;

		for (int j = 0; j < ENTRYS; j++, pte++) {
			if (*pte == 0) {
				continue;
			}

			page = (~0xfffUL & *pte) | DMW_MASK;
			if (is_share_page(page) && (*pte & PTE_D)) {
				for (i = 0; i < NR_SHMEM; i++) {
					if (shmem_table[i].mem == page) {
						shmem_table[i].count--;
						break;
					}
				}
			}

			free_page(page);
			*pte = 0;
		}

		free_page(*pde | DMW_MASK);
		*pde = 0;
	}
}

void copy_page_table(struct process *from, struct process *to) {
	unsigned long from_pd, to_pd, from_pt, to_pt;
	unsigned long *from_pde, *to_pde, *from_pte, *to_pte;
	unsigned long page;
	int i, j;

	from_pd = from->page_directory;
	from_pde = (unsigned long *)from_pd;
	to_pd = to->page_directory;
	to_pde = (unsigned long *)to_pd;
	for (i = 0; i < ENTRYS; i++, from_pde++, to_pde++) {
		if (*from_pde == 0) {
			continue;
		}

		from_pt = *from_pde | DMW_MASK;
		from_pte = (unsigned long *)from_pt;
		to_pt = get_page();
		to_pte = (unsigned long *)to_pt;
		*to_pde = to_pt & ~DMW_MASK;
		for (j = 0; j < ENTRYS; j++, from_pte++, to_pte++) {
			if (*from_pte == 0) {
				continue;
			}

			page = (~0xfffUL & *from_pte) | DMW_MASK;
			if (is_share_page(page) && (*from_pte & PTE_D)) {
				continue;
			}

			share_page(page);
			*from_pte &= ~PTE_D;
			*to_pte = *from_pte;
		}
	}

	invalidate();
}

void do_wp_page() {
	unsigned long *pte;
	unsigned long u_vaddr;
	unsigned long old_page, new_page;

	u_vaddr = read_csr_64(CSR_BADV);
	pte = get_pte(current, u_vaddr);
	old_page = (~0xfff & *pte) | DMW_MASK;

	if (is_share_page(old_page)) {
		new_page = get_page();
		*pte = (new_page & ~DMW_MASK) | PTE_PLV | PTE_D | PTE_V;
		copy_mem((char *)new_page, (char *)old_page, PAGE_SIZE);
		free_page(old_page);
	} else {
		*pte |= PTE_D;
	}

	invalidate();
}

void do_no_page() {
	unsigned long page;
	unsigned long u_vaddr;

	u_vaddr = read_csr_64(CSR_BADV);
	u_vaddr &= ~0xfffUL;
	page = get_page();
	if (u_vaddr < current->exe_end) {
		get_exe_page(u_vaddr, page);
	}

	put_page(current, u_vaddr, page, PTE_PLV | PTE_D | PTE_V);
}

void mem_init() {
	int i;

	for (i = 0; i < NR_PAGE; i++) {
		if (i >= KERNEL_START_PAGE && i < KERNEL_END_PAGE) { /* MaQueOS 将物理地址空间 0x200000 ~ 0x300000 预留给内核的二进制可执行代码 */
			mem_map[i] = 1;
		} else {
			mem_map[i] = 0;
		}
	}

	write_csr_64(CSR_DMW0_PLV0 | DMW_MASK, CSR_DMW0); /* 将特权级PLV0下使用的虚拟地址到物理地址转换模式配置为直接映射。 */
	write_csr_64(0, CSR_DMW3);
	write_csr_64((PWCL_EWIDTH << 30) | (PWCL_PDWIDTH << 15) | (PWCL_PDBASE << 10) | (PWCL_PTWIDTH << 5) | (PWCL_PTBASE << 0), CSR_PWCL);
	invalidate();
	shmem_init();
}
